package cn.zyjblogs.starter.common.utils.rsa;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * Copyright (C), 2021, 北京同创永益科技发展有限公司
 *
 * @author meiji
 * @version 3.0.0
 * @description RsaUtils
 * @date 2021/11/4 9:18
 */
@Slf4j
public final class RsaUtils {

    private static final String DEFAULT_ALGORITHM = "RSA";

    private RsaUtils() {
    }

    /**
     * 生成密钥对
     *
     * @param
     * @return cn.com.hatechframework.common.entity.dto.RsaKeyDto
     * @author meiji
     * @date 2021/11/4 9:43
     */
    public static void generateKey() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance(DEFAULT_ALGORITHM);
        // 密钥长度
        generator.initialize(1024, new SecureRandom());
        KeyPair keyPair = generator.generateKeyPair();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 密钥 Base64 字符串化
        String publicKey = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
        String privateKey = Base64.getEncoder().encodeToString((rsaPrivateKey.getEncoded()));
        System.out.println("公钥：\r\n" + publicKey);
        System.out.println("私钥：\r\n" + privateKey);
    }

    /**
     * 公钥加密
     *
     * @param rawStr
     * @param publicKey
     * @return java.lang.String
     * @author meiji
     * @date 2021/11/4 9:43
     */
    public static String publicKeyEncrypt(String rawStr, String publicKey) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(publicKey);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
        Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        return Base64.getEncoder().encodeToString(cipher.doFinal(rawStr.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 公钥解密
     *
     * @param encodeStr
     * @param publicKey
     * @return java.lang.String
     * @author meiji
     * @date 2021/11/4 9:43
     */
    public static String publicKeyDecrypt(String encodeStr, String publicKey) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(publicKey);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
        Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8))));
    }

    /**
     * 私钥加密
     *
     * @param rawStr
     * @param privateKey
     * @return java.lang.String
     * @date 2021/11/4 9:43
     */
    public static String privateKeyEncrypt(String rawStr, String privateKey) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey);
        return Base64.getEncoder().encodeToString(cipher.doFinal(rawStr.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 私钥解密
     *
     * @param encodeStr
     * @param privateKey
     * @return java.lang.String
     * @date 2021/11/4 9:43
     */
    public static String privateKeyDecrypt(String encodeStr, String privateKey) throws Exception {
        byte[] decoded = Base64.getDecoder().decode(privateKey);
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(DEFAULT_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        Cipher cipher = Cipher.getInstance(DEFAULT_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(encodeStr.getBytes(StandardCharsets.UTF_8))));
    }

    /**
     * 获取公钥
     *
     * @param path 公钥路径
     * @return 公钥
     * @date 2021/11/9 10:06
     */
    public static PublicKey getPublicKeyByPath(String path) {
        String publicKeyStr = null;
        try {
            publicKeyStr = IOUtils.toString(Paths.get(path).toUri(), StandardCharsets.UTF_8);
            return getPublicKeyByKeyStr(publicKeyStr);
        } catch (IOException e) {
            log.error("公钥读取失败");
            return null;
        }
    }

    /**
     * 获取公钥
     *
     * @param publicKeyStr 公钥解析为对象 PublicKey
     * @return 公钥
     * @date 2021/11/9 10:06
     */
    public static PublicKey getPublicKeyByKeyStr(String publicKeyStr) {
        try {
            publicKeyStr = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "");
            byte[] keyBytes = Base64.getMimeDecoder().decode(publicKeyStr);
            // 通过X509编码的Key指令获得公钥对象
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(DEFAULT_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            return publicKey;
        } catch (InvalidKeySpecException e) {
            log.error("获取公钥出错", e);
            return null;
        } catch (NoSuchAlgorithmException e) {
            log.error("不支持此算法", e);
            return null;
        } catch (Exception e) {
            return null;
        }
    }
}
